﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace Isuka
{

    /// <summary>
    /// 門番
    /// </summary>
    public static class Gate
    {

        /// <summary>
        /// index が [min, max) の範囲に収まっているかどうかをチェックし、
        /// 範囲外ならば例外を送出する
        /// </summary>
        /// <param name="index"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        public static void InRange(int index, int min, int max)
        {
            Contract.Ensures(min <= index && index < max);

            if (index < min || max <= index)
            {
                throw new IndexOutOfRangeException();
            }
        }

        /// <summary>
        /// index が Collection の範囲に収まっているかどうかをチェックし
        /// 範囲外ならば例外を送出する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="index"></param>
        /// <param name="collection"></param>
        public static void InRange<T>(int index, ICollection<T> collection)
        {
            Contract.Requires(collection != null);
            Contract.Ensures(0 <= index && index < collection.Count);

            if (index < 0 || collection.Count <= index)
            {
                throw new IndexOutOfRangeException();
            }
        }

        public static void InRange(int index, ICollection collection)
        {
            Contract.Requires(collection != null);
            Contract.Ensures(0 <= index && index < collection.Count);

            if (index < 0 || collection.Count <= index)
            {
                throw new IndexOutOfRangeException();
            }
        }

        public static void NotEmpty<T>(ICollection<T> collection)
        {
            Contract.Requires(collection != null);
            Contract.Ensures(collection.Count > 0);

            if (collection.Count <= 0)
            {
                throw new IndexOutOfRangeException(Messages.Error.E_NULL_OR_EMPTY);
            }
        }

        public static void NotEmpty(ICollection collection)
        {
            Contract.Requires(collection != null);
            Contract.Ensures(collection.Count > 0);

            if (collection.Count <= 0)
            {
                throw new InvalidOperationException(Messages.Error.E_NULL_OR_EMPTY);
            }
        }

        /// <summary>
        /// NULL であれば例外を送出することで、NULL が戻らないことを保証する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static T NotNull<T>(T value)
        {
            Contract.Ensures(Contract.Result<T>() != null);

            if ((object)value == null)
            {
                throw new InvalidOperationException(Messages.Error.E_NULL);
            }
            else
            {
                return value;
            }
        }

        /// <summary>
        /// NULL であれば例外を送出することで、NULL が戻らないことを保証する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="exceptionFactory"></param>
        /// <returns></returns>
        public static T NotNull<T>(T value, Func<Exception> exceptionFactory)
        {
            Contract.Requires<ArgumentNullException>(exceptionFactory != null, "exceptionFactory");
            Contract.Ensures(Contract.Result<T>() != null);

            if ((object)value == null)
            {
                var ex = exceptionFactory() ?? new InvalidOperationException(Messages.Error.E_NULL);
                throw ex;
            }
            else
            {
                return value;
            }
        }

        /// <summary>
        /// 正の値のみ有効
        /// </summary>
        /// <param name="value"></param>
        public static void Positive(int value)
        {
            Contract.Ensures(0 <= value);
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(Messages.Error.E_IS_MINUS);
            }
        }

        public static void RequireNotNull(object obj, string paramName = null)
        {
            Contract.Ensures(obj != null);
            Requires<ArgumentNullException>(obj != null, paramName);
        }

        /// <summary>
        /// 事前条件。
        /// ベースクラスやインターフェイスに事前条件が設定されていて、
        /// 新たに条件が追加出来ないとき等に利用
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <param name="condition"></param>
        /// <param name="message"></param>
        public static void Requires<TException>(bool condition, string message = null)
            where TException : Exception
        {
            Contract.Ensures(condition);
            if (!condition)
            {
                if (message == null)
                {
                    throw Activator.CreateInstance<TException>();
                }
                else
                {
                    throw Activator.CreateInstance(typeof(TException), message) as TException;
                }
            }
        }

        /// <summary>
        /// func が正常終了するまで、最大 retry 回 func を実行する。
        /// retry は 1 以上の値を指定する必要がある。
        /// </summary>
        /// <typeparam name="T">戻り値</typeparam>
        /// <param name="func">実行する関数</param>
        /// <param name="retry">試行回数</param>
        /// <returns></returns>
        public static T Retry<T>(Func<T> func, int retry)
        {
            Contract.Requires<ArgumentNullException>(func != null, "func");
            Contract.Requires<ArgumentException>(retry >= 1, "retry");

            Exception lastException = null;
            for (int i = 0; i < retry; i++)
            {
                try
                {
                    return func();
                }
                catch (Exception ex)
                {
                    lastException = ex;
                }
            }
            throw lastException;
        }

        /// <summary>
        /// func が正常終了するまで、最大 retry 回 func を実行する。
        /// retry は 1 以上の値を指定する必要がある。
        /// 戻り値が NULL にならないことを設計上保証する
        /// </summary>
        /// <typeparam name="T">戻り値</typeparam>
        /// <param name="func">実行する関数</param>
        /// <param name="retry">試行回数</param>
        /// <returns></returns>
        public static T RetryNotNull<T>(Func<T> func, int retry) where T : class
        {
            Contract.Requires(func != null);
            Contract.Requires(retry >= 1);
            Contract.Ensures(Contract.Result<T>() != null);

            return NotNull(Retry(func, retry));
        }
    }
}
